package com.luo.gmall.gateway.filter;

import com.luo.gmall.common.utils.IpUtil;
import com.luo.gmall.common.utils.JwtUtils;
import com.luo.gmall.gateway.config.JwtProperties;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author Zoro
 * @date 2021年11月21
 * 局部过滤器
 * <p>
 * 局部过滤器：拦截特定路由的请求
 * 1.编写一个类XxxGatewayFilterFactory实现GatewayFilterFactory接口 继承AbstractGatewayFilterFactory基类（推荐）
 * 2.在yml配置中配置过滤器
 * filters:
 * - Xxx
 * 如果向过滤器中传递固定数量的参数，需要额外实现一下步骤：
 * 3.定义静态内部类：KeyValueConfig
 * 4.继承AbstractGatewayFilterFactory基类时，指定泛型为KeyValueConfig实体类
 * 5.重写父类的无参构造方法，调用super(KeyValueConfig.class)
 * 6.重写shortcutFieldOrder方法，返回字段顺序
 * 如果要接受不定数量的参数。需要额外实现以下步骤：
 * 7.在实体类中定义集合字段
 * 8.重写shortcutType方法，返回：ShortcutType.GATHER_LIST
 */
@Component
@Slf4j
@EnableConfigurationProperties(JwtProperties.class)
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.PathConfig> {

    @Autowired
    private JwtProperties jwtProperties;

    public AuthGatewayFilterFactory() {
        super(PathConfig.class);
    }

    @Override
    public GatewayFilter apply(PathConfig config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // 获取请求对象，和响应对象
                ServerHttpRequest request = exchange.getRequest();
                ServerHttpResponse response = exchange.getResponse();
                // 1.获取当前请求的路径
                URI uri = request.getURI();
                String curlPath = uri.getPath();
                // 2.判断当前请求路径是否在请求参数的列表中（判断请求参数是否以参数列表中的值开头的）不在则放行
                if (config.paths.stream().noneMatch(curlPath::startsWith)) {
                    return chain.filter(exchange);
                }

                // 3.获取header中的token值
                String token = request.getHeaders().getFirst("token");
                // 判断token的值是否为空
                if (StringUtils.isBlank(token)) {
                    // 如果header中没有token值，那去cookie中去找GMALL-TOKEN
                    MultiValueMap<String, HttpCookie> cookies = request.getCookies();
                    if (!CollectionUtils.isEmpty(cookies) && cookies.containsKey(jwtProperties.getCookieName())) {
                        token = Objects.requireNonNull(cookies.getFirst(jwtProperties.getCookieName())).getValue();
                    }
                }

                // 4.判断token值是否为空，如果为空，重定向到登录页面
                if (StringUtils.isBlank(token)) {
                    // 设置响应状态
                    response.setStatusCode(HttpStatus.SEE_OTHER);
                    // 设置响应到的登录页面的地址
                    response.getHeaders().set(HttpHeaders.LOCATION, "http://sso.gmall.com/toLogin.html?returnUrl=" + uri);
                    // 返回
                    return response.setComplete();
                }
                try {
                    // 存在token， 解析token
                    Map<String, Object> map = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey());

                    String ipAtGateway = IpUtil.getIpAddressAtGateway(request);
                    String ipAtServer = map.get("ip").toString();
                    // 5.判断ip是否为同一个，不同则重定向到登录页面
                    if (!StringUtils.equals(ipAtGateway, ipAtServer)) {
                        // 设置响应状态
                        response.setStatusCode(HttpStatus.SEE_OTHER);
                        // 设置响应到的登录页面的地址
                        response.getHeaders().set(HttpHeaders.LOCATION, "http://sso.gmall.com/toLogin.html?returnUrl=" + uri);
                        // 返回
                        return response.setComplete();
                    }
                    // 6.传递登录信息到后续服务
                    // 将userId转变成request对象，mutate：转变的意思
                    request.mutate().header("userId", map.get("userId").toString())
                            .header("username", map.get("username").toString())
                            .build();
                    // 将新的request对象转变成exchange对象
                    exchange.mutate().request(request).build();
                } catch (Exception e) {
                    e.printStackTrace();
                    // 解析异常，重定向到登录页面
                    // 设置响应状态
                    response.setStatusCode(HttpStatus.SEE_OTHER);
                    // 设置响应到的登录页面的地址
                    response.getHeaders().set(HttpHeaders.LOCATION, "http://sso.gmall.com/toLogin.html?returnUrl=" + uri);
                    // 返回
                    return response.setComplete();
                }
                return chain.filter(exchange);
            }
        };
    }


    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("paths");
    }

    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.GATHER_LIST;
    }

    @Data
    public static class PathConfig {
        private List<String> paths;
    }

}
